
#include "stdafx.h"

#include "macros.h"

namespace ds {
   
/******************************************************************************/
/* ds::GetIndentation
/*
/******************************************************************************/
int GetIndent(const dsSTRING & text, dsVECTOR<dsSTRING> & lines);

int GetIndent(const dsSTRING & text) {
   dsVECTOR<dsSTRING> tmp;
   return GetIndent(text, tmp);
}

/******************************************************************************/
/* ds::GetIndentation
/*
/******************************************************************************/
int GetIndent(const dsSTRING & text, dsVECTOR<dsSTRING> & lines)
{
   lines.Resize(1);
   lines[0].Clear();
   for (int i = 0; i < text.Length(); ++i) {
      if (text[i] == '\n') {
         lines.PushBack();
         continue;
      }

      lines.Back() += text[i];
   }

   int minIndent = text.Length();

   for (int i = 0; i < lines.Length(); ++i) {
      dsSTRING & line = lines[i];
      line.TrimRight();

      if (line.IsEmpty()) {
         continue;
      }

      int indent = 0;
      for (int k = 0; k < line.Length(); ++k) {
         if (ds::IsSpace(line[k])) {
            ++indent;
         } else {
            break;
         }
      }

      m3dUpdateMin(minIndent, indent);
   }

   return minIndent;
}

/******************************************************************************/
/* ds::BuildStringFromLines
/*
/******************************************************************************/
dsSTRING BuildStringFromLines(const dsVECTOR<dsSTRING> & lines, int start = 0, int count = -1)
{
   if (count == -1) {
      count = lines.Length() - start;
   }

   dsSTRING res;
   for (int i = 0; i < count; ++i) {
      res += lines[i + start];
      if (i + 1 != count) {
         res += "\n";
      }
   }

   return res;
}

/******************************************************************************/
/* ds::SetIndent
/*
/******************************************************************************/
dsSTRING SetIndent(const dsSTRING & text, int indent)
{
   dsVECTOR<dsSTRING> lines;
   int prevIndent = GetIndent(text, lines);

   dsSTRING newIndent(' ', indent);

   dsSTRING res;
   for (int i = 0; i < lines.Length(); ++i) {
      dsSTRING & line = lines[i];

      if (!line.IsEmpty()) {
         res += newIndent;
         res += line.SubStr(prevIndent, line.Length() - prevIndent);
      }

      res += "\n";
   }

   return res;
}

}

dsSTRING MacrosPreprocess(const dsSTRING & text_, dsVECTOR<int> & lineCorrection)
{
   const char * outputMethod = Macros::OutputMethod.c_str();

   const dsSTRING text = text_ + "\n";

   enum STATE {
      START_LINE,
      LINE,
      SIMPLE_NAME,
      BLOCKED_NAME,
      CODE_BLOCK,
      CODE_BLOCK_LINE,
      CODE_BLOCK_LINE_START,
      CODE_BLOCK_OUT_LINE,
   } state = START_LINE;

   dsSTRING res;

   dsSTRING curLine;

   int blocksCount = 0;
   int lineNmb = 0;

   STATE nameInjectionPrevState = LINE;
   bool isCodeInjectionName = false;

   for (int index = 0; index < text.Length(); ++index) {
      char cur = text[index];
      char cur2 = (cur == '\n') ? 0 : text[index + 1];

      switch (state) {
         case START_LINE:
            curLine.Clear();

            if (cur == '$') {
               curLine += "str(";
               if (cur2 != '(') {
                  state = SIMPLE_NAME;
               } else {
                  blocksCount = 0;
                  ++index;
                  state = BLOCKED_NAME;
               }
               break;
            }
            if (cur == '\n') {
               res += outputMethod;
               res += "(\"\\n\")\n";
               ++lineNmb;
               continue;
            }
            if (cur == '<' && cur2 == '?') {
               ++index;
               curLine.Clear();
               state = CODE_BLOCK;
               break;
            }

            curLine += "\"";
            curLine += cur;
            state = LINE;
            break;

         case LINE:
            if (cur == '<' && cur2 == '?') {
               res += outputMethod;
               res += "(";
               res += curLine;
               res += "\")\n";
               curLine.Clear();

               ++lineNmb;
               lineCorrection += lineNmb;

               ++index;
               curLine.Clear();
               state = CODE_BLOCK;
               break;
            }

            switch (cur) {
               case '\n':
                  res += outputMethod;
                  res += "(";
                  res += curLine;
                  res += "\\n\")\n";
                  ++lineNmb;

                  state = START_LINE;
                  break;

               case '$':
                  curLine += "\" + str(";
                  nameInjectionPrevState = state;
                  isCodeInjectionName = false;
                  state = SIMPLE_NAME;
                  break;

               case '"':
               case '\\':
                  curLine += "\\";
                  curLine += cur;
                  break;

               default:
                  curLine += cur;
                  break;
            }
            break;

         case BLOCKED_NAME:
         case SIMPLE_NAME:
            if (cur == '\n') {
               ++lineNmb;

               if (!isCodeInjectionName) {
                  res += outputMethod;
                  res += "(";
                  res += curLine;
                  res += "))\n";

                  ++lineNmb;

                  state = nameInjectionPrevState;
               }
               break;
            }

            if (state == BLOCKED_NAME && cur == '(') {
               ++blocksCount;
            } else if (state != BLOCKED_NAME && !ds::IsWordLetter(cur) || state == BLOCKED_NAME && cur == ')' && --blocksCount < 0) {
               curLine += ") + \"";

               if (state != BLOCKED_NAME) {
                  --index;
               }

               state = nameInjectionPrevState;
               break;
            }

            curLine += cur;
            break;

         case CODE_BLOCK_LINE:
         case CODE_BLOCK_LINE_START:
         case CODE_BLOCK:
            if (cur == '?' && cur2 == '>') {
               dsVECTOR<dsSTRING> codeLines;
               ds::GetIndent(curLine, codeLines);
               
               dsSTRING firstLine = codeLines[0];
               res += ds::SetIndent(firstLine, 0);

               dsSTRING indentedCode = ds::SetIndent(ds::BuildStringFromLines(codeLines, 1), 0);
               res += indentedCode;

               ++lineNmb;
               lineCorrection += lineNmb;

               ++index;
               state = START_LINE;
               break;
            }

            if (cur == '>' && cur2 == '>' && state == CODE_BLOCK) {
               curLine += outputMethod;
               curLine += "(\"";
               state = CODE_BLOCK_OUT_LINE;
               ++index;

               if (text[index + 1] == ' ') {
                  ++index;
               }
               break;
            }

            if (state == CODE_BLOCK_LINE_START) {
               state = CODE_BLOCK_LINE;
            }
            if (cur == '"') {
               state = (state == CODE_BLOCK_LINE) ? CODE_BLOCK : CODE_BLOCK_LINE_START;
            }

            if (cur == '$' && (state == CODE_BLOCK_LINE || state == CODE_BLOCK_LINE_START)) {
               if (state == CODE_BLOCK_LINE_START) {
                  curLine = curLine.SubStr(0, curLine.Length() - 1);
                  curLine += "str(";
               } else {
                  curLine += "\" + str(";
               }

               nameInjectionPrevState = state;
               isCodeInjectionName = true;
               if (cur2 == '(') {
                  ++index;
                  blocksCount = 0;
                  state = BLOCKED_NAME;
               } else {
                  state = SIMPLE_NAME;
               }
               break;
            }

            if (cur == '\n') {
               ++lineNmb;
            }

            curLine += cur;
            break;

         case CODE_BLOCK_OUT_LINE:
            if (cur == '\n') {
               curLine += "\\n\")\n";
               ++lineNmb;
               state = CODE_BLOCK;
               break;
            }

            if (cur == '"' || cur == '\\') {
               curLine += "\\";
            }

            if (cur == '$') {
               curLine += "\" + str(";

               isCodeInjectionName = true;
               nameInjectionPrevState = state;

               if (cur2 == '(') {
                  ++index;
                  blocksCount = 0;
                  state = BLOCKED_NAME;
               } else {
                  state = SIMPLE_NAME;
               }
               break;
            }

            curLine += cur;
            break;
        }
   }

   return res;
}
